// WinPlace.cpp
//

#include "stdafx.h"
#include "WinPlace.h"

// registry strings
static const TCHAR gszValueName[] = _T("Windows");

WinPlace::WinPlace()
{
	length = sizeof(WINDOWPLACEMENT);
}

WinPlace::~WinPlace()
{
}

void WinPlace::Get(CWnd* pWnd)
{
	// Get window placement.
	pWnd->GetWindowPlacement(this);
	if((rcNormalPosition.left == 0) && (rcNormalPosition.top == 0))
	{
		int cx = rcNormalPosition.right - rcNormalPosition.left;
		int cy = rcNormalPosition.bottom - rcNormalPosition.top;
		rcNormalPosition.left = (GetSystemMetrics(SM_CXSCREEN) - cx) / 2;
		rcNormalPosition.top = (GetSystemMetrics(SM_CYSCREEN) - cy) / 2;
		rcNormalPosition.right = rcNormalPosition.left + cx;
		rcNormalPosition.bottom = rcNormalPosition.top + cy;
	}
}

void WinPlace::Set(CWnd* pWnd)
{
	// Set window placement.
	::MoveWindow(pWnd->m_hWnd,
				 rcNormalPosition.left,
				 rcNormalPosition.top,
				 rcNormalPosition.right - rcNormalPosition.left,
				 rcNormalPosition.bottom - rcNormalPosition.top,
				 TRUE);
//	pWnd->SetWindowPlacement(this);
}

BOOL WinPlace::Restore(CWnd* pWnd, LPCTSTR regkey, LPCTSTR name, BOOL bChange/* = FALSE*/)
{
	Get(pWnd);

	// Get window placement values from registry.
	HKEY hKey;
	LONG res;
	DWORD dwDisp;

	res = RegCreateKeyEx(HKEY_CURRENT_USER, regkey, 0, NULL,
						 REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
						 NULL, &hKey, &dwDisp);
	if(ERROR_SUCCESS != res)
	{
		Set(pWnd);
		::SetLastError(res);
		return FALSE;
	}

	CObList placementList;
	DWORD dwLen;
	DWORD dwType;

	res = RegQueryValueEx(hKey, gszValueName, 0, &dwType, NULL, &dwLen);
	if(ERROR_SUCCESS != res)
	{
		BOOL b = (ERROR_FILE_NOT_FOUND == res);

		if(b)
		{
			ActivatePlacement(&placementList, name, bChange);
			SavePlacementList(hKey, &placementList);
			CleanupPlacementList(&placementList);
		}
		Set(pWnd);
		RegCloseKey(hKey);
		::SetLastError(res);
		return b;
	}

	if(!((dwType == REG_MULTI_SZ)))
	{
		Set(pWnd);
		RegCloseKey(hKey);
		::SetLastError(ERROR_BAD_FORMAT);
		return FALSE;
	}

	if(dwLen == 0)
	{
		ActivatePlacement(&placementList, name, bChange);
		res = SavePlacementList(hKey, &placementList);
		Set(pWnd);
		RegCloseKey(hKey);
		CleanupPlacementList(&placementList);
		::SetLastError(res);
		return (ERROR_SUCCESS == res);
	}

	int size = dwLen / sizeof(TCHAR);
	TCHAR* pBuf = new TCHAR[size+1];

	res = RegQueryValueEx(hKey, gszValueName, 0, &dwType, (LPBYTE)pBuf, &dwLen);
	if(ERROR_SUCCESS != res)
	{
		Set(pWnd);
		delete(pBuf);
		RegCloseKey(hKey);
		::SetLastError(res);
		return FALSE;
	}

	pBuf[size] = _T('\0');
	ParsePlacements(pBuf, size, &placementList);

	BOOL b = ActivatePlacement(&placementList, name, bChange);
	res = SavePlacementList(hKey, &placementList);
	CleanupPlacementList(&placementList);
	Set(pWnd);
	RegCloseKey(hKey);
	res = b ? res : ERROR_CONNECTION_ACTIVE;
	::SetLastError(res);
	delete(pBuf);
	return (ERROR_SUCCESS == res);
}

BOOL WinPlace::ParsePlacements(LPTSTR pBuf, int size, CObList* pList)
{
	int len;
	int reminder = size;
	LPTSTR pb = pBuf;
	int x, y, act, order;

	TCHAR* parr = new TCHAR[size];
	len = _tcslen(pb);
	while((len > 0) && (reminder > 0))
	{
		if(5 != _stscanf(pb, _T("%s %i %i %i %i"), parr, &x, &y, &act, &order))
			break;

		PLACEMENT* place = new PLACEMENT;
		place->order = order;
		place->active = act;
		place->name = parr;
		place->position.x = x;
		place->position.y = y;
		place->size = 4*4 + len + 1;	//placement descriptor dirty size 
		pList->AddTail((CObject*)place);

		pb += (len + 1);
		reminder -= len;
		len = _tcslen(pb);
	}
	delete(parr);

	return (len == 0);
}

LONG WinPlace::SavePlacementList(HKEY hKey, CObList* pList)
{
	int total;
	POSITION pos;
	PLACEMENT* place;

	total = 1;
	pos = pList->GetHeadPosition();
	while(pos != NULL)
	{
		place = (PLACEMENT*)pList->GetNext(pos);
		total += place->size;
	}

	TCHAR* pBuf = new TCHAR[total];

	TCHAR* pb = pBuf;
	total = 1;
	pos = pList->GetHeadPosition();
	while(pos != NULL)
	{
		place = (PLACEMENT*)pList->GetNext(pos);
		_stprintf(pb, _T("%s %i %i %i %i"), (LPCTSTR)place->name, place->position.x, place->position.y, place->active, place->order);
		int len = _tcslen(pb);
		total += (len + 1);
		pb += len;
		*pb++ = _T('\0');
	}
	*pb++ = _T('\0');

	LONG res = RegSetValueEx(hKey, gszValueName, 0, REG_MULTI_SZ, (BYTE*)pBuf, total*sizeof(TCHAR));
	delete(pBuf);
	return res;
}

void WinPlace::CalcNewPosition(PLACEMENT* place, int x, int y, BOOL bReplace/* = TRUE*/)
{
	int offs = GetSystemMetrics(SM_CYCAPTION) + 4;
	int newX = x + offs;
	int newY = y + offs;

	CRect rc;

	CRect rcTemp;
	rcTemp.left = newX;
	rcTemp.top = newY;
	rcTemp.right = newX + rcNormalPosition.right - rcNormalPosition.left;
	rcTemp.bottom = newY + rcNormalPosition.bottom - rcNormalPosition.top;

	CRect rcScreen;
	rcScreen.left = 0;
	rcScreen.top = 0;
	rcScreen.right = GetSystemMetrics(SM_CXSCREEN);
	rcScreen.bottom = GetSystemMetrics(SM_CYSCREEN);

	BOOL b = rc.IntersectRect(&rcTemp, &rcScreen);
	if( !b || (rc != rcTemp) )
	{
		newX = offs;
		newY = offs;
	}
	place->position.x = newX;
	place->position.y = newY;
	if( bReplace )
	{
		rcNormalPosition.right = newX + rcNormalPosition.right - rcNormalPosition.left;
		rcNormalPosition.bottom = newY + rcNormalPosition.bottom - rcNormalPosition.top;
		rcNormalPosition.left = newX;
		rcNormalPosition.top = newY;
	}
}

BOOL WinPlace::ActivatePlacement(CObList* placementList, LPCTSTR pName, BOOL bChange)
{
	BOOL found;
	POSITION pos;
	PLACEMENT* place;
	PLACEMENT* placeUnnamed = NULL;
	PLACEMENT* placeLast = NULL;
	BOOL bRes = TRUE;
	int order = 0;

	pos = placementList->GetHeadPosition();
	while(pos != NULL)
	{
		place = (PLACEMENT*)placementList->GetNext(pos);
		if( place->name.Compare(_T("none")) == 0 )
		{
			placeUnnamed = place;
		}
		if( place->order > order )
		{
			order = place->order;
			placeLast = place;
		}
	}

	if( pName && (_tcscmp(pName, _T("none")) != 0) )
	{
		found = FALSE;
		pos = placementList->GetHeadPosition();
		while(pos != NULL)
		{
			place = (PLACEMENT*)placementList->GetNext(pos);
			if( place->name.Compare(pName) == 0 )
			{
				found = TRUE;
				break;
			}
		}
		if( !found )
		{
			place = new PLACEMENT;
			place->size = 4*4 + _tcsclen(pName) + 1;
			place->order = order + 1;
			place->active = 1;
			place->name = pName;
			if( placeLast )
			{
				CalcNewPosition(place, placeLast->position.x, placeLast->position.y);
			}
			else if( placeUnnamed )
			{
				CalcNewPosition(place, placeUnnamed->position.x, placeUnnamed->position.y);
			}
			else
			{
				place->position.x = rcNormalPosition.left;
				place->position.y = rcNormalPosition.top;
			}
			placementList->AddTail((CObject*)place);
			return TRUE;
		}
		else
		{
			if( place->active == 0 )
			{
//				place->order = 0;
				place->active = 1;
				if( bChange )
				{
					place->position.x = rcNormalPosition.left;
					place->position.y = rcNormalPosition.top;
					if(placeUnnamed && (placeUnnamed->active == 0) && (place->position == placeUnnamed->position))
					{
						CalcNewPosition(placeUnnamed, place->position.x, place->position.y, FALSE);
					}
				}
				else
				{
					int cx = rcNormalPosition.right - rcNormalPosition.left;
					int cy = rcNormalPosition.bottom - rcNormalPosition.top;
					rcNormalPosition.left = place->position.x;
					rcNormalPosition.top = place->position.y;
					rcNormalPosition.right = rcNormalPosition.left + cx;
					rcNormalPosition.bottom = rcNormalPosition.top + cy;
				}
				return TRUE;
			}
			bRes = FALSE;
		}
	}

	if( placeUnnamed == NULL )
	{
		placeUnnamed = new PLACEMENT;
		placeUnnamed->size = 4*4 + _tcsclen(_T("none")) + 1;
		placeUnnamed->active = 1;
		placeUnnamed->order = order + 1;
		placeUnnamed->name = _T("none");
		placeUnnamed->position.x = rcNormalPosition.left;
		placeUnnamed->position.y = rcNormalPosition.top;
		placementList->AddTail((CObject*)placeUnnamed);
		if( placeLast )
			CalcNewPosition(placeUnnamed, placeLast->position.x, placeLast->position.y);
	}
	else
	{
		placeUnnamed->active++;
		placeUnnamed->order = order + 1;
		if( bChange )
		{
			placeUnnamed->position.x = rcNormalPosition.left;
			placeUnnamed->position.y = rcNormalPosition.top;
		}
		else
		{
			if( placeUnnamed->active > 1 )
			{
				CalcNewPosition(placeUnnamed, placeUnnamed->position.x, placeUnnamed->position.y);
			}
			else
			{
				int cx = rcNormalPosition.right - rcNormalPosition.left;
				int cy = rcNormalPosition.bottom - rcNormalPosition.top;
				rcNormalPosition.left = placeUnnamed->position.x;
				rcNormalPosition.top = placeUnnamed->position.y;
				rcNormalPosition.right = rcNormalPosition.left + cx;
				rcNormalPosition.bottom = rcNormalPosition.top + cy;
			}
		}
	}

	return bRes;
}

void WinPlace::DeactivatePlacement(CObList* placementList, LPCTSTR pName)
{
	POSITION pos;
	PLACEMENT* place;
	CString str;

	if( pName && (_tcscmp(pName, _T("none")) != 0) )
		str = pName;
	else
		str = _T("none");

	pos = placementList->GetHeadPosition();
	while(pos != NULL)
	{
		place = (PLACEMENT*)placementList->GetNext(pos);
		if( place->name.Compare(str) == 0 )
		{
			if( --place->order <= 0 )
				place->order = 0;
			if( --place->active <= 0 )
			{
				place->active = 0;
				place->position.x = rcNormalPosition.left;
				place->position.y = rcNormalPosition.top;
			}
			break;
		}
	}
}

void WinPlace::CleanupPlacementList(CObList* pList)
{
	while(!pList->IsEmpty())
	{
		delete((PLACEMENT*)pList->RemoveHead());
	}
}

void WinPlace::Save(CWnd* pWnd, LPCTSTR regkey, LPCTSTR name)
{
	Get(pWnd);

	// Get window placement values from registry.
	HKEY hKey;
	LONG res;
	DWORD dwDisp;

	res = RegCreateKeyEx(HKEY_CURRENT_USER, regkey, 0, NULL,
						 REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
						 NULL, &hKey, &dwDisp);
	if(ERROR_SUCCESS != res)
		return;

	CObList placementList;
	DWORD dwLen;
	DWORD dwType;

	res = RegQueryValueEx(hKey, gszValueName, 0, &dwType, NULL, &dwLen);
	if(ERROR_SUCCESS != res)
	{
		if(ERROR_FILE_NOT_FOUND == res)
		{
			ActivatePlacement(&placementList, name, FALSE);
			DeactivatePlacement(&placementList, name);
			SavePlacementList(hKey, &placementList);
			CleanupPlacementList(&placementList);
		}
		RegCloseKey(hKey);
		return;
	}

	if(!((dwType == REG_MULTI_SZ)))
	{
		RegCloseKey(hKey);
		return;
	}

	if(dwLen == 0)
	{
		ActivatePlacement(&placementList, name, FALSE);
		DeactivatePlacement(&placementList, name);
		SavePlacementList(hKey, &placementList);
		RegCloseKey(hKey);
		CleanupPlacementList(&placementList);
		return;
	}

	int size = dwLen / sizeof(TCHAR);
	TCHAR* pBuf = new TCHAR[size+1];

	res = RegQueryValueEx(hKey, gszValueName, 0, &dwType, (LPBYTE)pBuf, &dwLen);
	if(ERROR_SUCCESS != res)
	{
		delete(pBuf);
		RegCloseKey(hKey);
		return;
	}

	pBuf[size] = _T('\0');
	ParsePlacements(pBuf, size, &placementList);
	DeactivatePlacement(&placementList, name);
	SavePlacementList(hKey, &placementList);
	CleanupPlacementList(&placementList);
	RegCloseKey(hKey);
	delete(pBuf);
}
